package ar.edu.itba.pod.legajo48062;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import ar.edu.itba.balance.api.NotCoordinatorException;
import ar.edu.itba.node.NodeInformation;

// ESTA CLASE OFRECE LOS METODOS BROADCAST, GOSSIPBROADCAST, UNICAST Y UNICASTWITHRETURN
// ENCAPSULA EL MANEJO DE OBJETOS REMOTOS, REGISTRY, EXCEPCIONES Y DESCONEXION DE NODOS EN CASO DE SER NECESARIO
// PARA UTILIZAR ESTA CLASE SE DEBE IMPLEMENTAR ALGUNO DE LOS DOS METODOS 
// - REMOTECALLWITHRETURN (EN CASO DE QUERER USAR UNICASTWITHRETURN O GOSSIPBROADCAST)
// - REMOTECALL (EN CASO DE QUERER USAR BROADCAST O UNICAST)

public class Communication<T> {
	private Cluster cluster;
	private String binding;
	
	public Communication(Cluster cluster,String binding) {
		this.cluster=cluster;
		this.binding=binding;
	}
	
	// Estos dos metodos no los puedo definir abstractos porque tienen que redefinir solo uno de los dos
	public void remoteCall(T stub, NodeInformation node) throws Exception{
		return;
	}
	
	public Object remoteCallWithReturn(T stub, NodeInformation node) throws Exception{
		return null;
	}

	// HAGO UN BROADCAST A MIS VECINOS
	// EXCEPTNODE: NODO A EXCEPTUAR (GENERALMENTE SE USA PARA EL NODO QUE ORIGINO EL MENSAJE)
	// DISCONNECT: SI HAY QUE DESCONECTAR O NO A UN NODO SI NO RESPONDE
	// EXCLUDEME: SI EL BROADCAST DEBE INCLUIRME A MI MISMO
	@SuppressWarnings("unchecked")
	public void broadcast(NodeInformation exceptNode, boolean disconnect, boolean excludeMe){
		try {
			for (NodeInformation n : cluster.connectedNodes()){
				if ((!(excludeMe && cluster.me().equals(n))) && (!(exceptNode!=null && exceptNode.equals(n)))){
					try {
						Registry remoteRegistry = LocateRegistry.getRegistry(n.host(),n.port());
						T stub = (T) remoteRegistry.lookup(binding);
						remoteCall(stub, n);
					} catch (Exception e) {
						if (disconnect){
							e.printStackTrace();
							cluster.disconnectFromGroupLocal(n);
						}
					}				
				}
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	
	// HAGO UN GOSSIP BROADCAST A MIS VECINOS. SI UN PORCENTAJE DE LOS VECINOS ME DEVUELVE TRUE DEJO DE ENVIAR
	@SuppressWarnings("unchecked")
	public void gossipBroadcast(NodeInformation exceptNode, boolean disconnect, boolean excludeMe){
		try {
			int countNodes = cluster.connectedNodes().size();
			if (excludeMe) countNodes--;
			if (exceptNode!=null && !exceptNode.equals(cluster.me())) countNodes--;
			int stopSendingValue=(int) Math.ceil( countNodes * Host.gossipStopProportion);
			int countAlreadyKnow = 0;
			
			for (NodeInformation n : cluster.connectedNodes()){
				if (!(excludeMe && cluster.me().equals(n)) && !(exceptNode!=null && exceptNode.equals(n))){
					try {
						Registry remoteRegistry = LocateRegistry.getRegistry(n.host(),n.port());
						T stub = (T) remoteRegistry.lookup(binding);
						boolean alreadyKnow = (Boolean) remoteCallWithReturn(stub, n);
						if (alreadyKnow){
							countAlreadyKnow++;
							if (countAlreadyKnow==stopSendingValue){
								return;
							}
						}
					} catch (Exception e) {
						if (disconnect){
							cluster.disconnectFromGroupLocal(n);
						}
					}				
				}
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}	
	
	// HAGO UN UNICAST AL NODO QUE ME INDICAN
	@SuppressWarnings("unchecked")
	public boolean unicast(NodeInformation destination, boolean disconnect){
		boolean success;
		try{
			Registry remoteRegistry = LocateRegistry.getRegistry(destination.host(),destination.port());
			T stub = (T) remoteRegistry.lookup(binding);
			remoteCall(stub, destination);
			success = true;
		}catch (NotCoordinatorException e) {
			success = unicast(e.getNewCoordinator(), disconnect);
		} catch(Exception e){
			if (disconnect){
				e.printStackTrace();
				cluster.disconnectFromGroupLocal(destination);
			}
			success = false;
		}		
		return success;
	}
	
	// HAGO UN UNICAST AL NODO QUE ME INDICAN Y DEVUELVO ALGO INDICADO EN EL REMOTECALLWITHRETURN
	@SuppressWarnings("unchecked")
	public Object unicastWithReturn(NodeInformation destination, boolean disconnect){
		try{
			Registry remoteRegistry = LocateRegistry.getRegistry(destination.host(),destination.port());
			T stub = (T) remoteRegistry.lookup(binding);
			return remoteCallWithReturn(stub, destination);
		}catch (NotCoordinatorException e) {
			return unicastWithReturn(e.getNewCoordinator(), disconnect);
		} catch(Exception e){
			if (disconnect){
				cluster.disconnectFromGroupLocal(destination);
			}
			return null;
		}		
	}
	
}
